package com.csii.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;


public class UploadHandleServlet2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //设置上传文件的最终保存目录: WEB-INF目录下,不允许外界直接访问,保证上传文件的安全性
        String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
        //上传时生成的临时文件的保存目录
        String tmpPath =this.getServletContext().getRealPath("/WEB-INF/tmp");
        File tmpFile = new File(tmpPath);
        //判断上传文件的保存目录是否存在
        if (!tmpFile.exists() && !tmpFile.isDirectory()) {
            System.out.println(tmpPath + " 临时目录不存在，需要创建!");
            //创建目录
            tmpFile.mkdir();
        }

        String message = "";//提示消息
        InputStream in = null;//输入流
        FileOutputStream out = null;//输出流
        ServletFileUpload servletFileUpload = null;//异常信息提取

        //使用Apache的文件上传组件处理文件上传
        try {
            //1-创建一个DisKFileItemFactory工厂
            DiskFileItemFactory factory = new DiskFileItemFactory();
            //设置工厂的缓冲区大小,当上传的文件大小超过此缓冲区大小时,就生成一个临时文件放到指定的临时目录中
            factory.setSizeThreshold(1024*100);//缓冲区大小为100K, 默认是10K
            //设置临时文件保存目录
            factory.setRepository(tmpFile);

            //2-创建一个文件上传解析器
            servletFileUpload = new ServletFileUpload(factory);
            //监听文件上传进度
            servletFileUpload.setProgressListener(new ProgressListener() {
                @Override
                public void update(long pBytesRead, long pContentLength, int pItems) {
                    System.out.println("当前上传文件总大小 : "+pContentLength
                            +" ,已上传文件总大小 : "+pBytesRead
                            +" ,已上传文件总个数 : "+pItems);
                }
            });
            //解决上传文件名的中文乱码
            servletFileUpload.setHeaderEncoding("UTF-8");
            //3-判断提交上来的数据是否是上传表单的数据
            if (!ServletFileUpload.isMultipartContent(request)) {
                //按照传统方式获取数据
                return;
            }
            servletFileUpload.setFileSizeMax(1024*1024);//上传单个文件大小不超过1M
            servletFileUpload.setSizeMax(1024*1024*10);//上传所有文件大小总和不超过10M

            //4-使用ServletFileUpload的解析器解析上传的数据,结果是List<FileItem>集合,
            //一个FileItem对应一个Form表单输入项
            List<FileItem> fileItemList =
                    servletFileUpload.
                            parseRequest(
                                    request);
            //遍历
            for (FileItem fileItem : fileItemList) {

                if (fileItem.isFormField()) {
                    //fileItem封装的是普通输入项的数据
                    String name = fileItem.getFieldName();
                    //解决普通输入项数据的中文乱码问题
                    String value = fileItem.getString("UTF-8");
                    //value = new String(value.getBytes("iso8859-1"),"UTF-8");
                    System.out.println(name + "  =  " + value);
                } else {
                    //fileItem封装的是上传文件

                    //得到上传文件的名称
                    String fileName = fileItem.getName();
                    System.out.println("上传文件名称fileName  : " + fileName);
                    //
                    if (fileName == null || "".equals(fileName.trim())) {
                        continue;
                    }

                    /**
                     * 不同浏览器提交的文件名是不同的
                     * 某些浏览器提交的文件名中包含路径, eg : c:\a\b\c.txt
                     * 某些浏览器提交的文件名中只有文件名, eg : c.txt
                     */
                    //只保留文件名
                    fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
                    //上传文件的扩展名
                    String fileExtName = fileName.substring(fileName.lastIndexOf(".")+1);
                    String extName = "txt"+"jpg"+"doc"+"zip";
                    //通过扩展名限制文件的上传类型
                    if ( !extName.contains(fileExtName) ){
                        throw new Exception("上传文件的扩展名非法,无法上传 : "+fileExtName);
                        //continue;
                    }

                    //获取fileItem中的上传文件的输入流
                    in = fileItem.getInputStream();
                    //创建文件的最终保存名称
                    String realFileName = makeFileName(fileName);
                    //创建文件的最终保存路径
                    String realSavePath = makePath(realFileName,savePath);

                    //创建文件输出流
                    out = new FileOutputStream(realSavePath + "\\" + realFileName);
                    //创建一个缓冲区
                    byte buffer[] = new byte[1024];
                    //判断输入流是否已经读取完毕的标识
                    int length = 0;
                    //循环将输入流读入取到缓冲区中
                    //(length = in.read(buffer)) > 0 表示 输入流中还有数据
                    while ((length = in.read(buffer)) > 0) {
                        //使用FileOutStream输出流将缓冲区的数据写入到指定目录中: savePath+"\\"+fileName
                        out.write(buffer, 0, length);
                    }

                    //删除处理文件上传时生成的临时文件
                    //fileItem.delete();
                    //提示信息-成功
                    message = " 文件上传成功 ";

                }

            }

        } catch (FileUploadBase.FileSizeLimitExceededException e){
            e.printStackTrace();
            System.out.println(" 单个文件的上传大小超过设置限定值1 : "+servletFileUpload.getFileSizeMax());
            message = " 单个文件的上传大小超过设置限定值 : "+servletFileUpload.getFileSizeMax();
            request.setAttribute("message",message);
            request.getRequestDispatcher("/message.jsp").forward(request,response);
            return;

        } catch (FileUploadBase.SizeLimitExceededException e){
            e.printStackTrace();
            System.out.println(" 当前上传所有文件的上传大小超过设置限定值2 : "+servletFileUpload.getSizeMax());
            message = " 当前上传所有文件大小超过设置限定值 : "+servletFileUpload.getSizeMax();
            request.setAttribute("message",message);
            request.getRequestDispatcher("/message.jsp").forward(request,response);
            return;

        } catch (Exception e) {
            //提示信息=失败
            message = " 文件上传失败 ";
            e.printStackTrace();
        } finally {
            //关闭输入流
            try {
                if ( in !=null )
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //关闭输出流
            try {
                if ( out !=null)
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(" 当前上传所有文件的上传大小超过设置限定值3 : "+servletFileUpload.getSizeMax());

        }

        request.setAttribute("message",message);
        request.getRequestDispatcher("/message.jsp").forward(request,response);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    /**
     * @method makeFileName
     * @description 生成上传的文件名, 格式: uuid+"_"+原始文件名
     * @author
     * @param orgFileName
     * @return uuid+"_"+原始文件名
     */
    private String makeFileName(String orgFileName){
        //每一个文件都有一个唯一文件名称,防止重名文件覆盖
        return UUID.randomUUID().toString()+"_"+orgFileName;
    }

    /**
     *
     * @param fileName
     * @param savePaht
     * @return
     */
    private String makePath(String fileName, String savePaht){
        //获取文件名的Hash值, 实质是 该文件名(字符串)的内存地址
        int hashCode = fileName.hashCode();
        int dir1 = hashCode & 0xF;//获取倒数最后一组最低4位,取值范围 1~15
        int dir2 = (hashCode & 0xF0)>>4;//获取倒数第二组4位,取值范围 1~15
        System.out.println("文件名的HashCode ===> "+hashCode);
        System.out.println("文件名的hashCode & 0xF ===> "+dir1);
        System.out.println("文件名的(hashCode & 0xF0)>>4 ===> "+dir2);

        //构造随机性的最终目录
        String saveRandomPath = savePaht+"\\"+dir1+"\\"+dir2;
        //File既可以代表文件 也可是代表目录
        File file = new File(saveRandomPath);
        //如果目录不存在
        //if ( !file.exists() && !file.isDirectory()){//文件不存在 & 目录不存在
        if ( !file.isDirectory()){//目录不存在
            //创建目录
            file.mkdirs();
        }

        return saveRandomPath;

    }


}

